#include "ModelUtil.h"

//-------------------------------------------------------------------------------------------------------------------
ModelUtil::ModelUtil()
{ }

//-------------------------------------------------------------------------------------------------------------------
ModelUtil::~ModelUtil()
{ }

//-------------------------------------------------------------------------------------------------------------------
ID3D11Buffer* ModelUtil::InitialiseVertexBuffer(ID3D11Device* device, vector<ModelType*>& modelData, XNA::AxisAlignedBox& modelAABB)
{
	BasicShader* shader = BasicShader::GetInstance();

	BasicShader::Vertex*			vertices;
	ID3D11Buffer*					vertexBuffer;
	D3D11_BUFFER_DESC				vbd;
	D3D11_SUBRESOURCE_DATA			vdata;
	HRESULT							result;
	
	int numVertices = modelData.size();
	
	// Create the vertex array.
	vertices = new BasicShader::Vertex[numVertices];
	if (!vertices)
		return false;

	// Create infinitely small minimum and maximum pointer vectors for the model's bounding box.
	XMFLOAT3 vMinf3(+MathHelper::Infinity, +MathHelper::Infinity, +MathHelper::Infinity);
	XMFLOAT3 vMaxf3(-MathHelper::Infinity, -MathHelper::Infinity, -MathHelper::Infinity);
	
	XMVECTOR vMin = XMLoadFloat3(&vMinf3);
	XMVECTOR vMax = XMLoadFloat3(&vMaxf3);

	// Load the vertex array and index array with data.
	for (int i = 0; i < numVertices; ++i)
	{
		// ... Apply any transformation matrices if necessary for the static model.

		ModelType* temp = modelData[i];

		vertices[i].Pos		= XMFLOAT3(temp->x, temp->y, temp->z);
		vertices[i].Tex		= XMFLOAT2(temp->tu, temp->tv);
		vertices[i].Normal	= XMFLOAT3(temp->nx, temp->ny, temp->nz);

		XMVECTOR P = XMLoadFloat3(&vertices[i].Pos);	// Load current vertex position.

		vMin = XMVectorMin(vMin, P);		// Update minimum position X,Y,Z values.
		vMax = XMVectorMax(vMax, P);		// Update maximum position X,Y,Z values.
	}

	// Build model's AABB
	XMStoreFloat3(&modelAABB.Center,  0.5f*(vMin+vMax));
	XMStoreFloat3(&modelAABB.Extents, 0.5f*(vMax-vMin));

	// Static vertex buffer description.
	vbd.Usage				= D3D11_USAGE_IMMUTABLE;
	vbd.ByteWidth			= sizeof(BasicShader::Vertex) * numVertices;
	vbd.BindFlags			= D3D11_BIND_VERTEX_BUFFER;
	vbd.CPUAccessFlags		= 0;
	vbd.MiscFlags			= 0;
	vbd.StructureByteStride = 0;

	vdata.pSysMem			= vertices;	// Point to vertex data.
	vdata.SysMemPitch		= 0;
	vdata.SysMemSlicePitch	= 0;

	result = device->CreateBuffer(&vbd, &vdata, &vertexBuffer);
	if (FAILED(result))
		return false;

	// Delete the vertices and indices arrays.
	delete vertices;
	vertices = nullptr;

	return vertexBuffer;
}

//-------------------------------------------------------------------------------------------------------------------
ID3D11Buffer* ModelUtil::InitialiseIndexBuffer(ID3D11Device* device, vector<ModelType*>& modelData)
{
	unsigned long*			indices;
	HRESULT					result;
	ID3D11Buffer*			indexBuffer;
	D3D11_BUFFER_DESC		ibd;
	D3D11_SUBRESOURCE_DATA	idata;

	int numIndices = modelData.size();

	// Create the index array.
	indices = new unsigned long[numIndices];
	if (!indices)
		return false;

	for (int i = 0; i < numIndices; ++i)
	{
		indices[i] = i;		// Index data.
	}

	// Static index buffer description.
	ibd.Usage				= D3D11_USAGE_DEFAULT;
	ibd.ByteWidth			= sizeof(unsigned long) * numIndices;
	ibd.BindFlags			= D3D11_BIND_INDEX_BUFFER;
	ibd.CPUAccessFlags		= 0;
	ibd.MiscFlags			= 0;
	ibd.StructureByteStride = 0;

	idata.pSysMem			= indices;	// Point to index data.
	idata.SysMemPitch		= 0;
	idata.SysMemSlicePitch	= 0;

	result = device->CreateBuffer(&ibd, &idata, &indexBuffer);
	if (FAILED(result))
		return false;

	delete indices;
	indices = nullptr;

	return indexBuffer;
}

//-------------------------------------------------------------------------------------------------------------------
bool ModelUtil::LoadModel(char* filename, vector<ModelType*>& modelData)
{
	ifstream fin;
	char input;
	int i;
	int numVertices;

	// Open the model file.
	fin.open(filename);

	// If it could not open the file then exit.
	if (fin.fail())
		return false;

	// Read up to the value of vertex count.
	fin.get(input);
	while(input != ':')
		fin.get(input);

	// Read the vertex count.
	fin >> numVertices;

	// Read up to the beginning of the data.
	fin.get(input);
	while (input != ':')
		fin.get(input);
	fin.get(input);
	fin.get(input);

	// Read in the vertex data.
	for (i=0; i<numVertices; ++i)
	{
		ModelType* temp = new ModelType;

		fin >> temp->x  >> temp->y  >> temp->z;
		fin >> temp->tu >> temp->tv;
		fin >> temp->nx >> temp->ny >> temp->nz;

		modelData.push_back(temp);
	}

	// Close the model file.
	fin.close();

	return true;
}

//-------------------------------------------------------------------------------------------------------------------
ID3D11Buffer* ModelUtil::BuildInstancedBuffer(ID3D11Device* device, vector<InstancedData>& instancedData)
{
	// Generates 100 instance world matrices and colours along a grid
	// to be applied to a model.

	const int n = 10;
	instancedData.resize(n*n);	// Set size of instanced data - n*n instances.

	float width = 100.0f;			// Width of grid.
	float depth = 100.0f;			// Depth of grid.

	float x		= -0.5f * width;	// Starting X is left of grid.
	float z		= -0.5f * depth;	// Starting Z is front of grid.

	float dx	= width / (n-1);	// X spacing = width / 4.
	float dz	= depth / (n-1);	// Z spacing = depth / 4.

	for (int k = 0; k < n; ++k)		// Z index
	{
		for (int j = 0; j < n; ++j)	// X index
		{
			// Position the instance along a 3D grid.
			instancedData[k*n + j].World = XMFLOAT4X4
				(
					1.0f,	0.0f, 0.0f,   0.0f,
					0.0f,	1.0f, 0.0f,	  0.0f,
					0.0f,	0.0f, 1.0f,	  0.0f,
					x+j*dx, 10.0f, z+k*dz, 1.0f		// World matrix only has translation data.
				);									// No rotation or scale.
		
			// Give the instance a random colour.
			int index = (k*n + j);

			instancedData[k*n + j].Color.x = MathHelper::RandF(0.0f, 1.0f);
			instancedData[k*n + j].Color.y = MathHelper::RandF(0.0f, 1.0f);
			instancedData[k*n + j].Color.z = MathHelper::RandF(0.0f, 1.0f);
			instancedData[k*n + j].Color.w = 1.0f;
		}
	}

	// Create an empty dynamic buffer to return to calling code.
	ID3D11Buffer* instancedBuffer;

	// Description for instanced buffer.
	D3D11_BUFFER_DESC vbd;
	vbd.Usage				= D3D11_USAGE_DYNAMIC;
	vbd.ByteWidth			= sizeof(InstancedData) * instancedData.size();
	vbd.BindFlags			= D3D11_BIND_VERTEX_BUFFER;
	vbd.CPUAccessFlags		= D3D11_CPU_ACCESS_WRITE;
	vbd.MiscFlags			= 0;
	vbd.StructureByteStride = 0;

	HR(device->CreateBuffer(&vbd, 0, &instancedBuffer));

	return instancedBuffer;
}

//-------------------------------------------------------------------------------------------------------------------
// Generates world matrices and colours for a particular meshID from a map text file for instancing.
ID3D11Buffer* ModelUtil::BuildSpecificInstancedBuffer(ID3D11Device* device, vector<InstancedData>& instancedData, 
	const string& meshID, const string& mapFilename)
{
	ifstream		 fin;
	string			 s_input;
	vector<XMFLOAT3> positions;
	
	// Read in position and type information.
	
	fin.open(mapFilename.c_str());

	if (fin.fail()) 
	{
		MessageBox(0, L"Unable to open map file!", L"Map Error", MB_OK);
		return NULL;
	}
	
	while (!fin.eof())
	{
		fin >> s_input;	  // Get first string on line.

		if (s_input.compare(meshID) == 0)	// If first word on line matches passed in meshID.
		{
			XMFLOAT3 tempPos;
			fin >> tempPos.x >> tempPos.y >> tempPos.z;
			positions.push_back(tempPos);
		}
	}

	// Resize instancedData vector to to fit amount of required data.
	int total = positions.size();
	instancedData.resize(total);

	// Build the world matrices based on read information.

	int k = 0;
	vector<XMFLOAT3>::iterator it = positions.begin();

	for (it, k; it != positions.end(); ++it, ++k)
	{
		float x = it->x;
		float y = it->y;
		float z = it->z;

		instancedData[k].World = XMFLOAT4X4
			(
				1.0f,	0.0f,	0.0f,	0.0f,
				0.0f,	1.0f,	0.0f,	0.0f,
				0.0f,	0.0f,	1.0f,	0.0f,
				x,		y,		z,      1.0f
			);	// Currently no rotation or scale.

		instancedData[k].Color.x = MathHelper::RandF(0.0f, 1.0f);
		instancedData[k].Color.y = MathHelper::RandF(0.0f, 1.0f);
		instancedData[k].Color.z = MathHelper::RandF(0.0f, 1.0f);
		instancedData[k].Color.w = 1.0f;	// Apply a random colour.
	}

	// Create an empty instanced buffer to return.
	ID3D11Buffer* instancedBuffer;

	// Description for instanced buffer.
	D3D11_BUFFER_DESC vbd;
	vbd.Usage				= D3D11_USAGE_DYNAMIC;
	vbd.ByteWidth			= sizeof(InstancedData) * instancedData.size();
	vbd.BindFlags			= D3D11_BIND_VERTEX_BUFFER;
	vbd.CPUAccessFlags		= D3D11_CPU_ACCESS_WRITE;
	vbd.MiscFlags			= 0;
	vbd.StructureByteStride = 0;

	HR(device->CreateBuffer(&vbd, 0, &instancedBuffer));

	return instancedBuffer;
}